package cn.zhx.xa.service.aop;

import cn.zhx.xa.service.annotation.RequestLimit;
import cn.zhx.xa.service.exception.BusinessException;

import cn.zhx.xa.model.vo.common.ResultCodeEnum;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Log4j2
public class RequestLimitAspect {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private HttpServletRequest request;

    // 切点 匹配所有被 requestLimit 注解标记的方法。
    @Pointcut("@annotation(requestLimit)")
    public void controllerAspect(RequestLimit requestLimit) {}

    //切面
    @Around("controllerAspect(requestLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        // get parameter from annotation
        Long period = requestLimit.period();
        Long limitCount = requestLimit.count();

        // request info
        String ip = request.getRemoteAddr();
        String uri = request.getRequestURL().toString();
        String key = "req_limit_".concat(uri).concat(ip);

        ZSetOperations zSetOperations = stringRedisTemplate.opsForZSet();

        // add current timestamp
        Long currentMs = System.currentTimeMillis();
        zSetOperations.add(key, String.valueOf(currentMs), currentMs);

        // set the expiration time for the code user
        stringRedisTemplate.expire(key, period, TimeUnit.SECONDS);

        // remove the value that out of current window
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);

        // check all available count
        Long count = zSetOperations.zCard(key);

        if (count > limitCount) {
            log.error("接口拦截：{} 请求超过限制频率【{}次/{}s】,IP为{}", uri, limitCount, period, ip);
            throw new BusinessException(ResultCodeEnum.FREQUENCY_TOO_FAST);
        }

        // execute the user request
        return  joinPoint.proceed();
    }

}
